import sys

import serial
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import QTimer, QDateTime, QRegExp
from PyQt5.QtWidgets import QWidget, QGridLayout, QDesktopWidget, QGroupBox, QFormLayout, QPushButton, QComboBox, \
    QLabel, QTextBrowser, QVBoxLayout, QHBoxLayout, QTextEdit, QCheckBox, QLineEdit, QApplication, QListWidget, \
    QListWidgetItem, QRadioButton, QButtonGroup, QDialog
from PyQt5.QtGui import QIcon, QRegExpValidator

MODBUS_FUN_CODE = ['Read Coils', 'Read Discrete Inputs', 'Read Holding Registers', 'Read Input Registers',
             'Write Single Coil', 'Write Single Register', 'Write Multiple Coils', 'Write Multiple Register']

class SetCoilsValueUi(QDialog):
    def __init__(self):
        super(SetCoilsValueUi, self).__init__()
        self.grid_layout = QGridLayout()
        self.label = QLabel('设置值')
        self.coil_on_value_rb = QRadioButton('on')
        self.coil_off_value_rb = QRadioButton('off')
        self.coils_btngroup = QButtonGroup()
        self.coils_btngroup.addButton(self.coil_on_value_rb, 1)
        self.coils_btngroup.addButton(self.coil_off_value_rb, 0)
        self.coil_on_value_rb.click()
        self.ok_btn = QPushButton('确定')
        self.cancel_btn = QPushButton('取消')
        self.grid_layout.addWidget(self.label, 0, 0)
        self.grid_layout.addWidget(self.coil_on_value_rb, 0, 1)
        self.grid_layout.addWidget(self.coil_off_value_rb, 0, 2)
        self.grid_layout.addWidget(self.ok_btn, 1, 1)
        self.grid_layout.addWidget(self.cancel_btn, 1, 2)
        self.setLayout(self.grid_layout)
        self.resize(300, 100)
        self.center()
        self.setWindowTitle('设置Coil值')
        self.setWindowFlag(QtCore.Qt.WindowContextHelpButtonHint, False)
        self.ok_btn.clicked.connect(self.accept)
        self.cancel_btn.clicked.connect(self.reject)


    # 控制窗口显示在屏幕中心的方法
    def center(self):
        # 获得窗口
        qr = self.frameGeometry()
        # 获得屏幕中心点
        cp = QDesktopWidget().availableGeometry().center()
        # 显示到屏幕中心
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def getcurrentvalue(self):
        return self.coils_btngroup.checkedId()

class SetRegisterValueUi(QDialog):
    def __init__(self):
        super(SetRegisterValueUi, self).__init__()
        self.grid_layout = QGridLayout()
        self.label = QLabel('设置值')
        self.data_value_text = QLineEdit('0')
        self.ok_btn = QPushButton('确定')
        self.cancel_btn = QPushButton('取消')
        self.grid_layout.addWidget(self.label, 0, 0)
        self.grid_layout.addWidget(self.data_value_text, 0, 1)
        self.grid_layout.addWidget(self.ok_btn, 1, 0)
        self.grid_layout.addWidget(self.cancel_btn, 1, 1)
        self.setLayout(self.grid_layout)
        self.resize(300, 100)
        self.center()
        self.setWindowTitle('设置16位有符号值(-32767-32767)')
        self.setWindowFlag(QtCore.Qt.WindowContextHelpButtonHint, False)
        self.ok_btn.clicked.connect(self.accept)
        self.cancel_btn.clicked.connect(self.reject)
    # 控制窗口显示在屏幕中心的方法
    def center(self):
        # 获得窗口
        qr = self.frameGeometry()
        # 获得屏幕中心点
        cp = QDesktopWidget().availableGeometry().center()
        # 显示到屏幕中心
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def getcurrentvalue(self):
        return int(self.data_value_text.text())

class ModbuslUi(QWidget):
    def __init__(self):
        super(ModbuslUi, self).__init__()
        # 初始化UI
        self.unit_ui()

    # 初始化UI
    def unit_ui(self):
        self.grid_layout = QGridLayout()  # 设置总体布局为表格布局 2行3列
        # addWidget用于添加组件
        self.grid_layout.addWidget(self.set_serial_setting_groupbox(), 0, 0)
        self.grid_layout.addWidget(self.set_serial_state_groupbox(), 1, 0)
        self.grid_layout.addWidget(self.set_receive_groupbox(), 0, 1)
        self.grid_layout.addWidget(self.set_mul_sent_groupbpx(), 0, 2)
        self.grid_layout.addWidget(self.set_single_sent_groupbox(), 1, 1, 1, 2)
        # 设置布局为grid_layout
        self.setLayout(self.grid_layout)
        # resize()方法调整窗口的大小
        self.resize(800, 600)
        # 设置窗口的图标
        self.setWindowIcon(QIcon('title_icon.png'))
        # 将窗口显示到中心
        self.center()
        # 设置窗口名
        self.setWindowTitle('Modbus通信助手')
        # 显示
        self.show()

    # 控制窗口显示在屏幕中心的方法
    def center(self):
        # 获得窗口
        qr = self.frameGeometry()
        # 获得屏幕中心点
        cp = QDesktopWidget().availableGeometry().center()
        # 显示到屏幕中心
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    # 串口设置 区
    def set_serial_setting_groupbox(self):

        # 设置一个 Modbus设置 分组框
        self.modbus_setting_gb = QGroupBox('Modbus设置')
        # 创建 Modbus设置 分组框内的布局管理器
        self.modbus_setting_formlayout = QFormLayout()
        self.modbus_setting_gb.setLayout(self.modbus_setting_formlayout)
        self.modbus_setting_formlayout.setSpacing(5)


        # 设置一个 通信模式选择 分组框
        self.conect_setting_gb = QGroupBox('通信模式')
        self.formlayout1 = QFormLayout()
        self.conect_setting_gb.setLayout(self.formlayout1)
        self.formlayout1.setSpacing(5)
        # 选择连接模式 下拉菜单
        self.mset_cb_connect_mode = QComboBox(self.conect_setting_gb)
        self.mset_cb_connect_mode.addItems(['Modbus RTU', 'Modbus TCP', 'Modbus RTU OVER TCP'])
        self.mset_cb_connect_mode.setCurrentIndex(1)
        self.formlayout1.addRow(self.mset_cb_connect_mode)
        self.modbus_setting_formlayout.addRow(self.conect_setting_gb)

        # 设置一个 串口 分组框
        self.serial_setting_gb = QGroupBox('串口设置')
        # 创建 串口设置 分组框内的布局管理器
        self.serial_setting_formlayout = QFormLayout()

        # 检测串口 按钮
        self.sset_btn_detect = QPushButton('检测串口')
        self.serial_setting_formlayout.addRow('串口选择：', self.sset_btn_detect)
        # 选择串口 下拉菜单
        self.sset_cb_choose = QComboBox(self.serial_setting_gb)
        # 添加一个下拉列表 由于没有标签 直接省略即可
        self.serial_setting_formlayout.addRow(self.sset_cb_choose)
        # 波特率 下拉菜单
        self.sset_cb_baud = QComboBox(self.serial_setting_gb)

        self.sset_cb_baud.addItems(['100', '300', '600', '1200', '2400', '4800', '9600', '14400', '19200',
                                    '38400', '56000', '57600', '115200', '128000', '256000'])
        self.serial_setting_formlayout.addRow('波特率：', self.sset_cb_baud)

        # 数据位 下拉菜单
        self.sset_cb_data = QComboBox(self.serial_setting_gb)
        self.sset_cb_data.addItems(['8', '7', '6', '5'])
        self.serial_setting_formlayout.addRow('数据位：', self.sset_cb_data)

        # 校验位 下拉菜单
        self.sset_cb_parity = QComboBox(self.serial_setting_gb)
        self.sset_cb_parity.addItems(['None', 'Even', 'Odd', 'Mark', 'Space'])  # 校验位N－无校验，E－偶校验，O－奇校验
        self.serial_setting_formlayout.addRow('校验位：', self.sset_cb_parity)

        # 停止位 下拉菜单
        self.sset_cb_stop = QComboBox(self.serial_setting_gb)
        self.sset_cb_stop.addItems(['1', '1.5', '2'])
        self.serial_setting_formlayout.addRow('停止位：', self.sset_cb_stop)
        # 流 控
        self.sset_cb_flow_control = QComboBox(self.serial_setting_gb)
        self.sset_cb_flow_control.addItems(['None', 'XON/XOFF', 'RTS/CTS', 'DSR/DTR'])
        self.serial_setting_formlayout.addRow('流 控：', self.sset_cb_flow_control)
        # 窗口配色 下拉菜单
        self.sset_cb_color = QComboBox(self.serial_setting_gb)
        self.sset_cb_color.addItems(['whiteblack', 'blackwhite', 'blackgreen'])
        self.serial_setting_formlayout.addRow('窗口配色：', self.sset_cb_color)

        self.serial_setting_formlayout.setSpacing(5)
        self.serial_setting_gb.setLayout(self.serial_setting_formlayout)

        self.modbus_setting_formlayout.addRow(self.serial_setting_gb)

        # 设置一个 网络设置 分组框
        self.net_setting_gb = QGroupBox('网络设置')
        self.formlayout2 = QFormLayout()

        # ip地址与端口号
        self.ip_address_text = QLineEdit('127.0.0.1')
        self.formlayout2.addRow('远程IP地址：', self.ip_address_text)
        self.ip_port_text = QLineEdit('503')
        self.formlayout2.addRow('服务器端口：', self.ip_port_text)

        self.net_setting_gb.setLayout(self.formlayout2)
        self.formlayout2.setSpacing(5)
        self.modbus_setting_formlayout.addRow(self.net_setting_gb)

        # 打开连接 按钮
        self.sset_btn_open = QPushButton('打开连接')
        self.sset_btn_open.setIcon(QIcon('open_button.png'))
        self.modbus_setting_formlayout.addRow(self.sset_btn_open)

        self.serial_setting_gb.setHidden(True)
        return self.modbus_setting_gb

    # 串口状态区
    def set_serial_state_groupbox(self):
        self.modbus_state_gb = QGroupBox('Modbus状态', self)
        self.serial_state_formlayout = QFormLayout()
        # modbus连接状态 标签
        self.ssta_lb_status = QLabel('未连接')
        self.serial_state_formlayout.addRow('Modbus状态：', self.ssta_lb_status)

        # 已发送 标签
        self.sent_count_num = 0
        self.ssta_lb_sent = QLabel(str(self.sent_count_num))
        self.serial_state_formlayout.addRow('已发送：', self.ssta_lb_sent)

        # 已接收 标签
        self.receive_count_num = 0
        self.ssta_lb_receive = QLabel(str(self.receive_count_num))
        self.serial_state_formlayout.addRow('已接收：', self.ssta_lb_receive)

        # 当前时间 标签
        self.ssta_lb_timer = QLabel(self)
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.showtime)
        self.timer.start()
        self.serial_state_formlayout.addRow(self.ssta_lb_timer)

        # 版本标签
        self.ssta_lb_version = QLabel('version：V1.0.0')
        self.serial_state_formlayout.addRow(self.ssta_lb_version)
        self.ssta_lb_coder = QLabel('author：Burrows liu')
        self.serial_state_formlayout.addRow(self.ssta_lb_coder)

        self.serial_state_formlayout.setSpacing(13)
        self.modbus_state_gb.setLayout(self.serial_state_formlayout)
        return self.modbus_state_gb

    def showtime(self):
        time_display = QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss dddd')
        self.ssta_lb_timer.setText(time_display)

    # 接收区
    def set_receive_groupbox(self):
        # 设置一个接收区分组框
        self.receive_gb = QGroupBox('数据显示', self)
        # 添加显示接收日志的文本框
        self.receive_log_view = QTextBrowser(self.receive_gb)
        # 在这里设置了最小宽度 防止接收窗口因拉动伸缩而变换的过小
        self.receive_log_view.setMinimumWidth(350)
        self.receive_log_view.append('Hello，欢迎使用Modbus通信助手！\n')
        # 设置布局并添加文本框
        self.vbox = QVBoxLayout()
        self.vbox.addWidget(self.receive_log_view)
        # 设置接收区分组框的布局
        self.receive_gb.setLayout(self.vbox)
        return self.receive_gb

    # 单条发送区
    def set_single_sent_groupbox(self):
        self.single_sent_gb = QGroupBox('单条发送', self)
        self.sins_overall_hlayout = QHBoxLayout(self.single_sent_gb)
        self.vlayout_1 = QVBoxLayout()
        self.glayout_1 = QGridLayout()

        # 多数据编辑框
        self.mul_data_send_listwidget = QListWidget()
        self.mul_data_send_listwidget.setMinimumWidth(350)
        # self.mul_data_send_listwidget.addItem(QListWidgetItem('D0000 = '))
        # self.mul_data_send_listwidget.addItem(QListWidgetItem('D0001 = '))
        # self.sins_te_send = QTextEdit()
        # self.sins_te_send.setMinimumWidth(350)
        self.vlayout_1.addWidget(self.mul_data_send_listwidget)

        # 循环发送 HEX发送 HEX接收 复选框
        self.sins_cb_loop_send = QCheckBox('循环发送')
        self.sins_le_loop_text = QLineEdit('500')
        reg = QRegExp('^(?!0)(?:[0-9]{1,6}|1000000)$')  # ^(?!0)(?:[0-9]{1,4}|10000)$
        reg_validator = QRegExpValidator(reg)
        self.sins_le_loop_text.setValidator(reg_validator)
        self.sins_lb_loop_label = QLabel('ms/次')
        # self.sins_cb_hex_receive = QCheckBox('HEX接收')
        # self.sins_cb_hex_send = QCheckBox('HEX发送')


        self.select_function_code = QLabel('选择功能码')
        self.function_code_cb = QComboBox()
        self.function_code_cb.addItems(MODBUS_FUN_CODE)
        self.function_code_cb.setCurrentText('Read Coils')
        self.slave_id_lb = QLabel('从站ID')
        self.slave_id_text = QLineEdit('1')
        self.slave_address_lb = QLabel('起始地址')
        self.slave_address_text = QLineEdit('0')
        self.quantity_lb = QLabel('读/写数量')
        self.quantity_le_text = QLineEdit('1')
        self.data_value_lb = QLabel('单个写入值')
        self.data_value_le_text = QLineEdit('1')
        self.data_value_lb.setHidden(True)
        self.data_value_le_text.setHidden(True)
        self.coil_value_lb = QLabel('设置线圈值')
        self.coil_value_lb.setHidden(True)
        self.coil_on_value_rb = QRadioButton('on')
        self.coil_on_value_rb.setHidden(True)
        self.coil_off_value_rb = QRadioButton('off')
        self.coil_off_value_rb.setHidden(True)
        self.coils_btngroup = QButtonGroup()
        self.coils_btngroup.addButton(self.coil_on_value_rb, 1)
        self.coils_btngroup.addButton(self.coil_off_value_rb, 0)
        self.coil_on_value_rb.click()
        self.glayout_1.addWidget(self.select_function_code, 0, 0)
        self.glayout_1.addWidget(self.function_code_cb, 0, 1, 1, 3)
        self.glayout_1.addWidget(self.sins_cb_loop_send, 1, 0)
        self.glayout_1.addWidget(self.sins_le_loop_text, 1, 1, 1, 2)
        self.glayout_1.addWidget(self.sins_lb_loop_label, 1, 3)
        self.glayout_1.addWidget(self.slave_id_lb, 2, 0)
        self.glayout_1.addWidget(self.slave_id_text, 2, 1)
        self.glayout_1.addWidget(self.slave_address_lb, 2, 2)
        self.glayout_1.addWidget(self.slave_address_text, 2, 3)

        self.glayout_1.addWidget(self.quantity_lb, 3, 0)
        self.glayout_1.addWidget(self.quantity_le_text, 3, 1, 1, 3)
        self.glayout_1.addWidget(self.data_value_lb, 4, 0)
        self.glayout_1.addWidget(self.data_value_le_text, 4, 1, 1, 3)
        self.glayout_1.addWidget(self.coil_value_lb, 5, 0)
        self.glayout_1.addWidget(self.coil_on_value_rb, 5, 1)
        self.glayout_1.addWidget(self.coil_off_value_rb, 5, 2)
        # 保存窗口 清除发送 发送 按钮
        self.sins_btn_save = QPushButton('保存窗口')
        self.sins_btn_clear = QPushButton('清除发送')
        self.command_btn_send = QPushButton('发送命令')
        self.glayout_1.addWidget(self.sins_btn_save, 6, 0, 1, 2)
        self.glayout_1.addWidget(self.sins_btn_clear, 6, 2, 1, 2)
        self.glayout_1.addWidget(self.command_btn_send, 7, 0, 1, 4)

        self.sins_overall_hlayout.addLayout(self.vlayout_1)
        self.sins_overall_hlayout.addLayout(self.glayout_1)
        self.single_sent_gb.setLayout(self.sins_overall_hlayout)
        return self.single_sent_gb

    # 多条发送区
    def set_mul_sent_groupbpx(self):
        self.mul_query_gb = QGroupBox('数据查询', self)
        self.mul_query_vlayout = QVBoxLayout()
        self.mul_query_gridlayout1 = QGridLayout()
        self.mul_query_gridlayout2 = QGridLayout()
        # 清除接收 按钮
        self.recvdata_btn_clear = QPushButton('清除接收')
        self.mul_query_gridlayout1.addWidget(self.recvdata_btn_clear, 0, 0, 1, 2)

        # 多条循环发送
        self.mul_start_query_btn = QPushButton('启动轮询')
        self.mul_query_gridlayout1.addWidget(self.mul_start_query_btn, 0, 2, 1 ,2)
        # self.query_interval_lb = QLabel('扫描间隔')
        # self.mul_query_gridlayout1.addWidget(self.query_interval_lb, 1, 1)
        # self.mul_le_loop_text = QLineEdit('1000')
        # reg = QRegExp('^(?!0)(?:[0-9]{1,6}|1000000)$')
        # reg_validator = QRegExpValidator(reg)
        # self.mul_le_loop_text.setValidator(reg_validator)
        # self.mul_query_gridlayout1.addWidget(self.mul_le_loop_text, 1, 2, 1, 1)
        # self.mul_lb_loop_lable = QLabel('ms')
        # self.mul_query_gridlayout1.addWidget(self.mul_lb_loop_lable, 1, 3)

        self.query_fun_code_lb = QLabel('选择功能码')
        self.query_fun_code_cb = QComboBox()
        self.query_fun_code_cb.addItems(MODBUS_FUN_CODE[0:4])
        self.query_fun_code_cb.setCurrentText('Read Holding Registers')
        self.mul_query_gridlayout1.addWidget(self.query_fun_code_lb, 1, 0)
        self.mul_query_gridlayout1.addWidget(self.query_fun_code_cb, 1, 1, 1, 3)


        self.query_salve_address_lb = QLabel('从站ID')
        self.query_salve_address_text = QLineEdit('1')
        self.query_start_address_lb = QLabel('起始地址')
        self.query_start_address_text = QLineEdit('0')
        self.query_quantity_lb = QLabel('读取数量')
        self.query_quantity_text = QLineEdit('1')
        self.query_data_format = QComboBox()
        # self.query_data_format.addItems()
        self.add_query_content_btn = QPushButton('添加查询块')
        self.del_query_content_btn = QPushButton('删除查询块')
        self.query_context = QListWidget()
        self.mul_query_gridlayout1.addWidget(self.query_salve_address_lb, 2, 0)
        self.mul_query_gridlayout1.addWidget(self.query_salve_address_text, 2, 1)
        self.mul_query_gridlayout1.addWidget(self.query_start_address_lb, 2, 2)
        self.mul_query_gridlayout1.addWidget(self.query_start_address_text, 2, 3)
        self.mul_query_gridlayout1.addWidget(self.query_quantity_lb, 3, 0)
        self.mul_query_gridlayout1.addWidget(self.query_quantity_text, 3, 1)
        self.mul_query_gridlayout1.addWidget(self.add_query_content_btn, 4, 0, 1, 2)
        self.mul_query_gridlayout1.addWidget(self.del_query_content_btn, 4, 2, 1, 2)
        self.mul_query_gridlayout1.addWidget(self.query_context, 5, 0, 1, 4)

        self.mul_query_vlayout.addLayout(self.mul_query_gridlayout1)
        self.mul_query_gb.setLayout(self.mul_query_vlayout)
        self.mul_query_gb.setMinimumWidth(320)
        return self.mul_query_gb


if __name__ == '__main__':
    # 每一PyQt5应用程序必须创建一个应用程序对象
    app = QApplication(sys.argv)

    # 创建一个SerialAssistant对象
    su = ModbuslUi()
    su.show()
    # 系统exit()方法确保应用程序干净的退出
    # exec_()方法的作用是“进入程序的主循环直到exit()被调用”，
    # 如果没有这个方法，运行的时候窗口会闪退
    sys.exit(app.exec_())






